#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <Judy.h>

#include "normalized_datatypes.h"
#include "globals.h"
#include "locals.h"

static unsigned char buffer[16];
#define BUILD_EXISTS_RESTRICTION_ID(r,f) snprintf((char*) buffer,16,"%d_%d",r,f)

int normalized_atomic_concept_count = 0;
int normalized_atomic_role_count = 0;
// int generated_atomic_concept_count = 0;
int generated_exists_restriction_count = 0;
int generated_subclass_axiom_count = 0;
int generated_subrole_axiom_count = 0;
int singleton_premise_count = 0;
int max_lhs_size = 0;
int max_rhs_size = 0;
int cum_lhs_size = 0;
int cum_rhs_size = 0;

Pvoid_t normalized_exists_restrictions = (Pvoid_t) NULL;
Pvoid_t normalized_conjunctions = (Pvoid_t) NULL;

// judy arrays for storing generated atomic concepts information
Pvoid_t generated_atomic_concepts_right = (Pvoid_t) NULL;
Pvoid_t generated_atomic_concepts_left = (Pvoid_t) NULL;

int generated_atomic_concepts_left_count = 0;
int generated_atomic_concepts_right_count = 0;


// get the existential restriction with role r and filler f from hash
NormalizedConcept* get_exists_restriction(int r, int f) {
	PWord_t pvalue;

	BUILD_EXISTS_RESTRICTION_ID(r,f);
	JSLG(pvalue, normalized_exists_restrictions, buffer); 
	if (pvalue == NULL)
		return NULL;
	return (NormalizedConcept*) *pvalue;
}

// put the existential restriction with role r and filler f into hash
void put_exists_restriction(int r, int f, NormalizedConcept* c) {
	PWord_t pvalue;

	BUILD_EXISTS_RESTRICTION_ID(r,f);
	JSLI(pvalue, normalized_exists_restrictions, buffer);
	if (pvalue == PJERR) {
		fprintf(stderr, "could not insert existential restriction (some %d %d), aborting", r, f);
		exit(-1);
	}
	*pvalue = (Word_t) c;
}

// get or create the existential restriction with role r and filler f
NormalizedConcept* get_create_normalized_exists(NormalizedRole* r, NormalizedConcept* f) {
	NormalizedConcept* c;
	NormalizedExists** tmp;

	// first check if we already created a normalized existential 
	// restriction with the same role and filler
	if ((c = get_exists_restriction(r->description.atomic->id,f->description.atomic->id)) != NULL) {
		return c;
	}
	// if it does not already exist, create it
	c = (NormalizedConcept*) malloc(sizeof(NormalizedConcept));
	assert(c != NULL);
 	c->type = NORMALIZED_EXISTENTIAL_RESTRICTION;
 	c->description.exists = (NormalizedExists*) malloc(sizeof(NormalizedExists));
 	assert(c->description.exists != NULL);
 	c->description.exists->role = r;
 	c->description.exists->filler = f;

	c->description.exists->subsumees = (Pvoid_t) NULL;

	c->singleton_premises_count = 0;
	c->singleton_premises = NULL;
	c->containing_premises_count = 0;
	c->containing_premises = NULL;

	++generated_exists_restriction_count;

	put_exists_restriction(r->description.atomic->id,f->description.atomic->id,c);
	// add filler of information to f
	tmp = realloc(f->description.atomic->filler_of, (f->description.atomic->number_of_filler_of + 1) * sizeof(NormalizedExists*));
	assert(tmp != NULL);
	f->description.atomic->filler_of = tmp;
	f->description.atomic->filler_of[f->description.atomic->number_of_filler_of] = c->description.exists;
	++(f->description.atomic->number_of_filler_of);
	
 	return c;
}


void put_generated_atomic_concept(NormalizedConcept* generated_atomic_concept, NormalizedConcept* normalized_filler, enum axiom_side side) {
	Word_t* pvalue;

	switch (side) {
		case LEFT_HANDSIDE:
			JLI(pvalue, generated_atomic_concepts_left, (Word_t) normalized_filler);
			if (pvalue == PJERR) {
				fprintf(stderr, "could not insert generated atomic concept, aborting!\n");
				exit(-1);
			}
			break;
		case RIGHT_HANDSIDE:
			JLI(pvalue, generated_atomic_concepts_right, (Word_t) normalized_filler);
			if (pvalue == PJERR) {
				fprintf(stderr, "could not insert generated atomic concept, aborting!\n");
				exit(-1);
			}
			break;	
		default:
			fprintf(stderr, "axiom side undefined, aborting\n");
			exit(-1);
	}
	*pvalue = (Word_t) generated_atomic_concept;
}


NormalizedConcept* get_generated_atomic_concept(NormalizedConcept* normalized_filler, enum axiom_side side) {
	PWord_t pvalue = NULL;

	switch (side) {
		case LEFT_HANDSIDE:
			JLG(pvalue, generated_atomic_concepts_left, (Word_t) normalized_filler); 
			break;
		case RIGHT_HANDSIDE:
			JLG(pvalue, generated_atomic_concepts_right, (Word_t) normalized_filler); 
			break;
	}
	if (pvalue == NULL)
		return NULL;
	return (NormalizedConcept*) *pvalue;
}


NormalizedConcept* get_normalized_conjunction(NormalizedConcept** conjuncts, int size) {
	int i;
	char buffer[128];
	char tmp[11];
	PWord_t pvalue;

	Pvoid_t x = (Pvoid_t) NULL;
	Word_t index = 0;
	int judy_return;

	buffer[0] = '\0';
	tmp[0] = '\0';

	for (i = 0; i < size; ++i) {
		J1S(judy_return, x, (Word_t) conjuncts[i]);
		if (judy_return == JERR) {
			fprintf(stderr, "could not set x, aborting!\n");
			exit(-1);
		}
	}
/*
	qsort(conjuncts, size, sizeof(NormalizedConcept*), compare_concepts);
	for (i = 0; i < size; ++i) {
		printf("XXX:%p:\n", conjuncts[i]);
		snprintf(tmp, 10, "%p", conjuncts[i]);
		strcat(buffer, tmp);
		strcat(buffer, "_");
	}
*/
	J1F(judy_return, x, index);
	while (judy_return == 1) {
		snprintf(tmp, 10, "%p", (NormalizedConcept*) index);
		strcat(buffer, tmp);
		strcat(buffer, "_");
		J1N(judy_return, x, index);	
	}
	// printf("get normalized_conjunction:%s:\n", buffer);

	JSLG(pvalue, normalized_conjunctions, (unsigned char*) buffer); 
	if (pvalue == NULL) {
		// printf("not found\n");
		return NULL;
	}
	// printf("FOUND!\n");
	return (NormalizedConcept*) *pvalue;
}

void put_normalized_conjunction(NormalizedConcept* conjunction) {
	NormalizedConcept** conjuncts;
	int conjunct_count, judy_return, i;
	Word_t index;
	PWord_t pvalue;
	char buffer[128];
	char tmp[11];

	buffer[0] = '\0';
	tmp[0] = '\0';
	J1C(conjunct_count, conjunction->description.conj->conjuncts, 0, -1);
	conjuncts = (NormalizedConcept**) malloc(conjunct_count * sizeof(NormalizedConcept*));
	assert(conjuncts != NULL);
	i = 0;
	index = 0;
	J1F(judy_return, conjunction->description.conj->conjuncts, index);
	while (judy_return == 1) {
		conjuncts[i++] = (NormalizedConcept*) index;
		J1N(judy_return, conjunction->description.conj->conjuncts, index);
	}
	// qsort(conjuncts, conjunct_count, sizeof(NormalizedConcept*), compare_concepts);
	for (i = 0; i < conjunct_count; ++i) {
		// printf("yyy:%p:\n", conjuncts[i]);
		snprintf(tmp, 10, "%p", conjuncts[i]);
		strcat(buffer, tmp);
		strcat(buffer, "_");
	}
	free(conjuncts);
	// printf("put normalized_conjunction:%s:\n", buffer);
	JSLI(pvalue, normalized_conjunctions, (unsigned char*) buffer);
	if (pvalue == PJERR) {
		fprintf(stderr, "could not insert normalized conjunction, aborting\n");
		exit(-1);
	}
	*pvalue = (Word_t) conjunction;
}

NormalizedConcept* get_create_normalized_conjunction(NormalizedConcept** conjuncts, int size) {
	NormalizedConcept* c;
	int i, judy_return;

	c = get_normalized_conjunction(conjuncts, size);
	if (c == NULL) {
		c = (NormalizedConcept*) malloc(sizeof(NormalizedConcept));
		assert(c != NULL);
		c->type = NORMALIZED_CONJUNCTION;
		c->description.conj = (NormalizedConjunction*) malloc(sizeof(NormalizedConjunction));
		assert(c->description.conj != NULL);

		c->description.conj->conjuncts = (Pvoid_t) NULL;
		for (i = 0; i < size; ++i) {
			J1S(judy_return, c->description.conj->conjuncts, (Word_t) conjuncts[i]);
			if (judy_return == JERR) {
				fprintf(stderr,"could not add conjunct, aborting!\n");
				exit(-1);
			}
		}
		put_normalized_conjunction(c);
	}
	return c;
}

NormalizedConcept* get_create_normalized_atomic_concept(Concept* c) {
	NormalizedConcept* ac = normalized_atomic_concepts[c->description.atomic->id];
	// first check if it already exists
	if (ac != NULL)
		return ac;

	ac = (NormalizedConcept*) malloc(sizeof(NormalizedConcept));
	assert(ac != NULL);
	ac->type = NORMALIZED_ATOMIC_CONCEPT;
	ac->description.atomic = (NormalizedAtomicConcept*) malloc(sizeof(NormalizedAtomicConcept));
	assert(ac->description.atomic != NULL);
	ac->description.atomic->id = c->description.atomic->id;
	// name of the original atomic concept is reused. shouldn't be freed!
	ac->description.atomic->name = c->description.atomic->name;
	ac->description.atomic->subsumers = (Pvoid_t) NULL;
	ac->description.atomic->subsumees = (Pvoid_t) NULL;
	ac->singleton_premises_count = 0;
	ac->singleton_premises = NULL;
	ac->containing_premises_count = 0;
	ac->containing_premises = NULL;
	// ac->description.atomic->concepts_to_process = (Pvoid_t) NULL;
	// ac->description.atomic->number_of_concepts_to_process = 0;
	// ac->description.atomic->concepts_to_process =  NULL;
	// ac->description.atomic->concepts_to_process.first = ac->description.atomic->concepts_to_process.last = NULL;
	init_queue(&ac->description.atomic->concepts_to_process);
	ac->description.atomic->number_of_filler_of = 0;
	ac->description.atomic->filler_of = NULL;

	normalized_atomic_concepts[ac->description.atomic->id] = ac;

	return ac;
}


// TODO:
NormalizedRole* get_create_normalized_atomic_role(Role* r) {
	NormalizedRole* ar = normalized_atomic_roles[r->description.atomic->id];
	if (ar != NULL)
		return ar;

	ar = (NormalizedRole*) malloc(sizeof(NormalizedRole));
	assert(ar != NULL);
	ar->type = NORMALIZED_ATOMIC_ROLE;
	ar->description.atomic = (NormalizedAtomicRole*) malloc(sizeof(NormalizedAtomicRole));
	assert(ar->description.atomic != NULL);
	ar->description.atomic->id = r->description.atomic->id;
	ar->description.atomic->name = r->description.atomic->name;
	ar->description.atomic->subsumers = NULL;
	ar->description.atomic->subsumees = NULL;

	normalized_atomic_roles[ar->description.atomic->id] = ar;
	
	return ar;
}

// for generating new atomic concepts needed during normalization
NormalizedConcept* create_new_normalized_atomic_concept(enum axiom_side side) {
	char name[24];
	NormalizedConcept* c;
	NormalizedConcept** tmp;

	c = (NormalizedConcept*) malloc(sizeof(NormalizedConcept));
	assert(c != NULL);
	c->type = NORMALIZED_ATOMIC_CONCEPT;
	c->description.atomic = (NormalizedAtomicConcept*) malloc(sizeof(NormalizedAtomicConcept));
	assert(c->description.atomic != NULL);

	switch (side) {
		case LEFT_HANDSIDE:
			snprintf(name,24,"_CREATED_L_%d",generated_atomic_concepts_left_count);
			// c->description.atomic->id = generated_atomic_concepts_left_count;
			c->description.atomic->id = generated_atomic_concepts_left_count + generated_atomic_concepts_right_count;
			++generated_atomic_concepts_left_count;
			break;
		case RIGHT_HANDSIDE:
			snprintf(name,24,"_CREATED_R_%d",generated_atomic_concepts_right_count);
			// c->description.atomic->id = generated_atomic_concepts_right_count;
			c->description.atomic->id = generated_atomic_concepts_left_count + generated_atomic_concepts_right_count;
			++generated_atomic_concepts_right_count;
			break;
	}
	c->description.atomic->name = (char*) malloc((strlen(name) + 1) * sizeof(char));
	assert(c->description.atomic->name != NULL);
	strcpy(c->description.atomic->name, name);
	c->description.atomic->subsumers = (Pvoid_t) NULL;
	c->description.atomic->subsumees = (Pvoid_t) NULL;
	c->singleton_premises_count = 0;
	c->singleton_premises = NULL;
	c->containing_premises_count = 0;
	c->containing_premises = NULL;
	// c->description.atomic->concepts_to_process = (Pvoid_t) NULL;
	// c->description.atomic->number_of_concepts_to_process = 0;
	// c->description.atomic->concepts_to_process =  NULL;
	// c->description.atomic->concepts_to_process.first = c->description.atomic->concepts_to_process.last = NULL;
	init_queue(&c->description.atomic->concepts_to_process);
	c->description.atomic->number_of_filler_of = 0;
	c->description.atomic->filler_of = NULL; 

	tmp = realloc(normalized_atomic_concepts, (normalized_atomic_concept_count + 1) * sizeof(NormalizedConcept*));
	assert(tmp != NULL);
	normalized_atomic_concepts = tmp;
	normalized_atomic_concepts[normalized_atomic_concept_count] = c;
	++normalized_atomic_concept_count;
	// for normalization stats
	// ++generated_atomic_concept_count;

	return c;
}

void add_normalized_axiom(NormalizedAxiom* ax, NormalizedTBox* n_tbox) {
	
	NormalizedAxiom** tmp;
	
	tmp = realloc(n_tbox->axioms, (n_tbox->axiom_count + 1) * sizeof(NormalizedAxiom*));
	assert(tmp != NULL);
	n_tbox->axioms = tmp;
	n_tbox->axioms[n_tbox->axiom_count] = ax;
	++(n_tbox->axiom_count);
}


NormalizedAxiom* create_normalized_subclass_axiom(NormalizedConcept* lhs, NormalizedConcept* rhs) {
	int conjunct_count;

 	NormalizedAxiom* ax = (NormalizedAxiom*) malloc(sizeof(NormalizedAxiom));
	assert(ax != NULL);
	ax->type = NORMALIZED_SUBCLASS_AX;
	ax->body.subclass_ax = (NormalizedSubClassAxiom*) malloc(sizeof(NormalizedSubClassAxiom));
	assert(ax->body.subclass_ax != NULL);
	ax->body.subclass_ax->lhs = lhs;
	ax->body.subclass_ax->rhs = rhs;

	// for normalization stats
	if (lhs->type == NORMALIZED_CONJUNCTION) {
		// get the size of conjunction
		J1C(conjunct_count,lhs->description.conj->conjuncts,0,-1);
		cum_lhs_size += conjunct_count;
		if (conjunct_count > max_lhs_size) 
			max_lhs_size = conjunct_count;
	}
	else {
		++singleton_premise_count;
		max_lhs_size = 1;
		cum_lhs_size += 1;
	}
	if (rhs->type == NORMALIZED_CONJUNCTION) {
		// get the size of conjunction
		J1C(conjunct_count,rhs->description.conj->conjuncts,0,-1);
		cum_rhs_size += conjunct_count;
		if (conjunct_count > max_rhs_size) 
			max_rhs_size = conjunct_count;
	}
	else
		cum_rhs_size += 1;
		

	++generated_subclass_axiom_count;

        return ax;
}


NormalizedAxiom* create_normalized_subrole_axiom(NormalizedRole* lhs, NormalizedRole* rhs) {
        NormalizedAxiom* ax = (NormalizedAxiom*) malloc(sizeof(NormalizedAxiom));
        assert(ax != NULL);
        ax->type = NORMALIZED_SUBROLE_AX;
        ax->body.subrole_ax = (NormalizedSubRoleAxiom*) malloc(sizeof(NormalizedSubRoleAxiom));
        assert(ax->body.subrole_ax != NULL);
        ax->body.subrole_ax->lhs = lhs;
        ax->body.subrole_ax->rhs = rhs;

	++generated_subrole_axiom_count;

        return ax;
}

